﻿#include "study14.h"
/*
文件操作主要内容：
    1. 打开文件
        std::ifstream 用法：
            std::ifstream file("文件名", std::ios_base::openmode); 打开文件失败时，会抛出 std::ios_base::failure 异常。
            可以通过 file.open() 方法打开文件，也可以通过 file.close() 方法关闭文件。
            打开成功后，可以使用 file.is_open() 方法判断文件是否打开成功；该函数返回 bool 值；true 表示打开成功，false 表示打开失败。
            也可以使用 file.fail() 方法判断文件是否打开失败。
        在 std::ios_base 命名空间中，有五种 openmode 选项：
            std::ios_base::in -> 以读模式打开文件，默认值。
            std::ios_base::out -> 以写模式打开文件。
            std::ios_base::ate -> 文件指针指向文件末尾。
            std::ios_base::app -> 文件指针指向文件末尾，以追加模式打开文件。
            std::ios_base::trunc -> 如果文件存在，则截断文件（设置文件大小为 0）；如果文件不存在，则创建文件。
            以上选项可以组合使用，如：std::ios_base::in | std::ios_base::out | std::ios_base::ate 表示打开文件用于读写，指针指向文件末尾
    2. 读写文件注意事项
        std::ifstream file("文件名", std::ios_base::openmode); 打开文件
        file.read(buffer, size); // 读取文件内容，buffer 是一个 char 数组，size 是数组大小
        file.seekg(offset, std::ios_base::seekdir) -> 设置文件指针位置; offset 是偏移量，seekdir 是偏移方向，可以是 std::ios_base::beg、std::ios_base::cur、std::ios_base::end。如：
            file.seekg(0, std::ios_base::beg); // 设置文件指针指向文件开头向后偏移 0 字节处
            file.seekg(0, std::ios_base::end); // 设置文件指针指向文件末尾向前偏移 0 字节处
            file.seekg(offset, std::ios_base::cur); // 设置文件指针指向当前位置向后偏移 offset 字节处
        file.write(buffer, size); // 写入文件内容，buffer 是一个 char 数组，size 是数组大小
        file.eof() -> 判断文件是否到达文件末尾。
        file.tellg() -> 返回文件指针位置。
        file.getline(buffer, size, delimiter) -> 读取一行内容；buffer 是一个 char 数组，size 是数组大小，delimiter 是行分隔符。
        file.close(); // 关闭文件，会自动调用析构函数，不需要手动调用。同时将缓冲区内容写入文件。
        file.flush(); // 刷新缓冲区内容到文件。

        file.exceptions(std::ios_base::failbit | std::ios_base::badbit |
        std::ios_base::eofbit); // 设置异常状态，默认值为 std::ios_base::goodbit
        file.clear(); // 清除异常状态，将文件指针指向文件开头。
        file.exceptions(std::ios_base::goodbit); // 关闭异常状态，恢复默认值。
        file.exceptions() -> 返回当前异常状态。
        file.rdstate() -> 返回当前状态。
        file.rdbuf() -> 返回文件缓冲区指针。
        file.tie() -> 返回与文件关联的流对象指针。
        file.set_rdbuf(buf) -> 设置文件缓冲区指针。

        file.swap(file2) -> 交换两个文件对象。
        file.putback(ch) -> 退回一个字符。
        file.unget() -> 退回一个字符。
        file.width() -> 设置输出字段宽度。
        file.fill() -> 设置填充字符。

在 ATL 库中，Format 函数的签名通常是这样的：
    LPTSTR Format(LPCTSTR pszFormat, LPTSTR pszBuffer) const;
    或者在某些重载版本中，可以直接返回一个分配好的字符串：
        CString Format(LPCTSTR pszFormat) const;
    CW2A 类型在 ATL 中定义，可用于字符串编码类型转换。CW2A.m_psz 成员变量是一个指向以 null 结尾的 ANSI 字符串的指针；可用于将 CString 转化为 std::string

在 C++17 及以上版本，可以使用 std::filesystem 命名空间，可以方便地操作文件系统。
    std::filesystem.path 类可以表示文件路径，可以方便地进行路径拼接、分解、比较等操作。
    std::filesystem.directory_iterator 类可以遍历目录中的所有条目，可以判断是否是普通文件、目录等。
    std::filesystem.create_directory 类可以创建目录。
    std::filesystem.remove 类可以删除文件或目录。
    std::filesystem.rename 类可以重命名文件或目录。
    std::filesystem.exists 类可以判断文件或目录是否存在。
    std::filesystem.file_size 类可以获取文件大小。
    std::filesystem.last_write_time 类可以获取文件最后修改时间。
    std::filesystem.permissions 类可以获取文件或目录的权限。
    std::filesystem.status 类可以获取文件或目录的状态。
    std::filesystem.copy 类可以复制文件。
    std::filesystem.is_empty 类可以判断目录是否为空。
    std::filesystem.is_directory 类可以判断是否是目录。
    std::filesystem.is_regular_file 类可以判断是否是普通文件。
    std::filesystem.is_other 类可以判断是否是其他文件。
    std::filesystem.is_symlink 类可以判断是否是符号链接。
    std::filesystem.is_block_file 类可以判断是否是块设备文件。
    std::filesystem.is_character_file 类可以判断是否是字符设备文件。
    std::filesystem.is_fifo 类可以判断是否是命名管道文件。
    std::filesystem.is_socket 类可以判断是否是套接字文件。
    std::filesystem.is_device 类可以判断是否是设备文件。
    std::filesystem.is_mount_point 类可以判断是否是挂载点。
    std::filesystem.is_empty 类可以判断目录是否为空。
    std::filesystem.is_other 类可以判断是否是其他文件。
    std::filesystem.is_symlink 类可以判断是否是符号链接。

*/

class FileOpenError : public std::runtime_error {
public:
    FileOpenError(const std::string& fileName)
        : std::runtime_error("无法打开文件: " + fileName) {}
};

// 打开文件
std::ifstream open_file(std::string fileName, std::ios_base::openmode mode = std::ios_base::in) {
    std::ifstream file(fileName, mode);
    if (!file.is_open())
        throw FileOpenError(fileName);
    return file;
}

// 读文件
static std::string read_file(std::string fileName) {
    std::ifstream file = open_file(fileName);
    std::string content((std::istreambuf_iterator<char>(file)),
        std::istreambuf_iterator<char>());
    return content;
}

// 写文件
static void write_file(std::string fileName, std::string content, bool append = false) {
    if (append) {
        std::ofstream file(fileName, std::ios_base::app);
        file << content; // 写入文件内容
        std::cout << "写入" << file.tellp() << "字节到文件" << std::endl; // 获取文件指针位置
        file.close();
    }
    else {
        std::ofstream file(fileName);
        file << content; // 写入文件内容
        std::cout << "写入" << file.tellp() << "字节到文件" << std::endl; // 获取文件指针位置
        file.close();
    }
}



namespace c11_file_path {
    bool ListFilesInDirectory(const std::string& path) {
        // 检查路径是否存在以及是否是目录，需要 windows.h 头文件
        WIN32_FIND_DATAA data; // 定义 WIN32_FIND_DATAA 结构体 (用于获取文件信息(包括文件名、创建时间、修改时间等))
        HANDLE hFind = FindFirstFileA((path + "/*").c_str(), &data); // 打开目录，并获取第一个文件信息，HANDLE 就是 void* 
        //#define INVALID_HANDLE_VALUE ((HANDLE)(LONG_PTR)-1)   // LONG_PTR 这里 x64 就是 long long，在 64 位系统中为 64 位整型，在 32 位系统中为 32 位整型
        if (hFind == INVALID_HANDLE_VALUE) {
            // 路径不存在或者不是一个目录
            std::cerr << "路径不存在或者不是一个目录。" << std::endl;
            return false;
        }

        do {
            if (strcmp(data.cFileName, ".") != 0 && strcmp(data.cFileName, "..") != 0)
                // 排除 . 和 .. 目录（上一级目录和当前目录）
                std::cout << data.cFileName << std::endl;
        } while (FindNextFileA(hFind, &data) != 0);

        if (GetLastError() != ERROR_NO_MORE_FILES) {
            // 表示查找文件时发生错误
            std::cerr << "查找文件时发生错误。" << std::endl;
            return false;
        }

        // 关闭目录
        FindClose(hFind);
        return true;
    }

    class DirectoryHandler {
    public:
        bool create_directory_windows(const std::string& dir_path_mb) {
            // 计算宽字符字符串所需的长度
            int dir_path_length = MultiByteToWideChar(CP_UTF8, 0, dir_path_mb.c_str(), -1, NULL, 0);
            // 分配内存给宽字符字符串
            LPWSTR dir_path_w = new WCHAR[dir_path_length];
            // 进行转换
            MultiByteToWideChar(CP_UTF8, 0, dir_path_mb.c_str(), -1, dir_path_w, dir_path_length);

            if (CreateDirectory(dir_path_w, NULL))
                std::wcout << L"目录 " << dir_path_w << L" 创建成功。" << std::endl;
            else
                std::wcerr << L"目录 " << dir_path_w << L" 创建失败。 错误代码: "
                << GetLastError() << std::endl;

            // 释放内存
            delete[] dir_path_w;
            return GetLastError() == ERROR_SUCCESS;
        }
    };
}



void test_file1() {
    // 写入文件内容：
    std::string text1 = "Hello, world!\n";
    const char* write1 = "写入现在的时间：";
    CTime now = CTime::GetCurrentTime();
    CString fmt_time = now.Format(_T("%Y-%m-%d %H:%M:%S")); // 格式化时间，CString 类型是 Unicode 类型，_T() 为 L 现在这种时间一般都用数据库做
    CW2A ansi_time(fmt_time); // 将 CString 类型转换为 ANSI 类型，CW2A 类型在 namespace ATL 中定义，可用于字符串编码类型转换
    std::string text2(ansi_time.m_psz); // 将 ANSI 类型转换为 std::string 类型
    //std::cout << text2 << std::endl;
    //TCHAR szBuffer[20] = { 0 }; // 确保缓冲区足够大以容纳格式化的字符串，TCHAR 类型是宽字符类型

    write_file("test.txt", text1);
    write_file("test.txt", write1, true);
    write_file("test.txt", text2, true);

    // 读取文件内容：
    std::string content = read_file("test.txt");
    std::cout << content << std::endl;
}

void test_file2() {
    std::string str1 = read_file("test.txt");
    std::fstream file2("test_copy.txt", std::ios::out | std::ios::trunc); // 打开文件，以写模式打开，并清空文件内容
    file2.write(str1.c_str(), str1.size()); // 复制文件内容到新文件
    file2.close(); // 关闭文件

    std::fstream file("test_copy.txt", std::ios::in | std::ios::out | std::ios::ate); // 打开文件
    std::cout << "文件最后位置：" << file.tellp() << std::endl; // 获取文件指针位置
    file.seekp(6, std::ios::beg); // 设置文件指针指向文件开头向后偏移 6 字节处
    file.write("a test\n", 7); // 写入文件内容
    std::cout << file.tellp() << std::endl; // 获取文件指针位置
    file.seekg(0, std::ios::beg); // 重置文件指针用于读取
    char buffer[100];
    file.getline(buffer, 100); // 使用 getline 读取一行到字符数组中
    std::cout << buffer << std::endl; // 输出读取的行
    file.close(); // 关闭文件
}

#if defined(USE_CPP17)
namespace fs = std::filesystem;
static bool list_directory_files(const std::string& path) {
    // 检查路径是否存在以及是否是目录，只适用于 C++17 及以上版本
    try
    {
        // 检查路径是否存在以及是否是目录
        if (fs::exists(path) && fs::is_directory(path))
        {
            // 遍历路径下的所有条目
            for (const auto& entry : fs::directory_iterator(path)) // C++17 及以上版本，使用 std::filesystem 命名空间，迭代器
                // 检查是否是普通文件
                if (entry.is_regular_file()) // C++17 及以上版本，检查是否是普通文件
                    // 输出文件名
                    std::cout << entry.path().filename().string() << std::endl;
                else if (entry.is_directory()) // C++17 及以上版本，检查是否是目录
                    // 输出目录名
                    std::cout << entry.path().filename().string() << std::endl;
            return true;
        }
        else {
            std::cout << "路径不存在或者不是一个目录。" << std::endl;
            return false;
        }
    }
    catch (const fs::filesystem_error& e) {
        std::cerr << "文件系统错误: " << e.what() << std::endl;
        return false;
    }
}
#endif

void test_path() {
    //#define MAX_PATH 260
    char buffer[MAX_PATH];  // 定义缓冲区，MAX_PATH 定义在 windows.h 头文件中
    if (_getcwd(buffer, MAX_PATH) != nullptr)
        std::cout << "当前路径: " << buffer << std::endl;
    else
        std::cerr << "无法获取当前路径。" << std::endl;

#if defined(USE_CPP17)
    fs::path p = fs::current_path(); // 获取当前路径，C++17 标准
    std::cout << "当前路径：" << p.string() << "   " << p.root_name() << "   " << p.root_directory() << "   " << p.relative_path() << "   " << p.parent_path() << "   " << p.filename() << "   " << p.stem() << "   " << p.extension() << std::endl; // 输出当前路径
    std::cout << "-----------------------------------------------------\n";
    // 列出目录中的文件
    std::string path = p.string(); // 要列出的目录路径
    if (list_directory_files(path))
        std::cout << "列出目录中的文件成功。" << std::endl;
    std::cout << "-----------------------------------------------------\n";
    if (c11_file_path::ListFilesInDirectory(path))
        std::cout << "再次列出目录中的文件成功。" << std::endl;
    std::cout << "-----------------------------------------------------\n";
    // 创建目录，复制文件，重命名文件，删除文件
    std::string new_dir = "新目录C17";
    if (fs::create_directory(new_dir))  // C++17 标准
        std::cout << "创建目录成功。" << std::endl;
    else
        std::cerr << "创建目录失败。" << std::endl;
    /*std::string file_name = "test_next.txt";
    std::string new_file_name = "test_next_copy.txt"; */
    write_file(p.string() + "\\" + new_dir + "\\" + "test_next.txt", "随便写一句话，只有在C++17及以上版本才看得到");
    fs::copy(new_dir + "\\" + "test_next.txt", p.string() + "\\" + new_dir + "\\" + "test_next_copy.txt");
    fs::rename(p.string() + "\\" + new_dir + "\\" + "test_next_copy.txt", p.string() + "\\" + new_dir + "\\" + "test_next_copy_rename.txt");
    fs::remove(p.string() + "\\" + new_dir + "\\" + "test_next.txt");
    std::cout << "------------------------------------\n";
    // 删除目录
    //fs::remove_all(new_dir); // C++17 标准，删除目录及其所有内容
#endif

#pragma execution_character_set("utf-8")    // 设置编译环境为UTF-8编码，可用于网页显示中文
    const char* new_dir2 = "新目录C11"; // 可以是任意路径，比如 c:\test\新目录C11；或者相对路径 ../ 表示上级目录，直接写名称是针对当前项目（本文件的 C++study15_文件正则异常 路径）所在路径的，也就是本项目原代码的所在路径
    c11_file_path::DirectoryHandler handler;
    handler.create_directory_windows(new_dir2); // 调用 windows API 创建目录
    //#pragma execution_character_set("default")    // 还原编译环境
}




static void save_as_binary(const FileData& data) {
    // 打开二进制文件以进行写入，第一个字符表示文件名的长度，然后后面跟着字符串内容
    std::ofstream outFile(data.filename, std::ios::binary);
    if (!outFile) {
        std::cerr << "无法打开文件进行写入: " << data.filename << std::endl;
        return;
    }

    /*
    // 保存 filename
    {
        // 写入字符串长度
        size_t len = data.filename.length();
        outFile.write(reinterpret_cast<const char*>(&len), sizeof(size_t));
        
        // 写入字符串内容
        if (len > 0) {
            outFile.write(data.filename.c_str(), len);
        }
    }
    */

    // 保存 file_content
    {
        // 写入字符串长度
        uint64_t len = data.file_content.length();
        outFile.write(reinterpret_cast<const char*>(&len), sizeof(uint64_t));
        
        // 写入字符串内容
        if (len > 0) {
            outFile.write(data.file_content.c_str(), len);
        }
    }

    // 检查写入是否成功
    if (!outFile) {
        std::cerr << "写入文件失败: " << data.filename << std::endl;
    }
    else {
        std::cout << "数据已成功写入到 " << data.filename << std::endl;
    }

    // 关闭文件
    outFile.close();
}

static void read_binary_file(FileData& data, const std::string& filename) {
    // 打开二进制文件以进行读取，读取第一个字符
    data.filename = filename; // 设置文件名
    std::ifstream inFile(filename, std::ios::binary);
    if (!inFile) {
        std::cerr << "无法打开文件进行读取: " << filename << std::endl;
        return;
    }

    /*
    // 读取 filename
    {
        // 读取字符串长度
        size_t len;
        inFile.read(reinterpret_cast<char*>(&len), sizeof(size_t));
        
        // 根据长度读取字符串内容
        if (len > 0) {
            std::string buffer;
            buffer.resize(len);
            inFile.read(&buffer[0], len);
            data.filename = buffer;
        }
    }
    */

    // 读取 file_content
    {
        // 读取字符串长度
        uint64_t len;
        inFile.read(reinterpret_cast<char*>(&len), sizeof(uint64_t));
        
        // 根据长度读取字符串内容
        if (len > 0) {
            std::string buffer;
            buffer.resize(len);
            inFile.read(&buffer[0], len);
            data.file_content = buffer;
        }
    }

    // 检查读取是否成功
    if (!inFile) {
        std::cerr << "读取文件失败或文件不完整: " << filename << std::endl;
    }
    else {
        std::cout << "数据已成功从 " << filename << " 中读取\n";
        std::cout << "Filename: " << data.filename << std::endl;
        std::cout << "File Content: " << data.file_content << std::endl;
    }

    // 关闭文件
    inFile.close();
}

static void print_file_data(const FileData& data) {
    std::cout << "Filename: " << data.filename << std::endl;
    std::cout << "File Content: " << data.file_content << std::endl;
}

void test_binary_file() {
    FileData data;
    data.filename = "test_binary.bin";
    data.file_content = "这是一串中文字符串，用于测试二进制文件读写。\nsecond line content of 'this is English string'. end\n";
    save_as_binary(data);

    FileData data_read;
    read_binary_file(data_read, "test_binary.bin");
    print_file_data(data_read);
}
